import math, random, weakref
import pyglet
from pyglet.gl import *
import resource
from vec2d import Vec2d
from fastsprite import Sprite
from robots import RobotScrap, RedRobot, YellowRobot, GreenRobot, PurpleRobot, ArmorRobot
from towers import Tower, CannonTower, GlueTower

class GameMap(object):
    def __init__(self, window):
        self.window = window
        self.objects = []
        self.deleted_objects = set([])

        # The intro label that appears at beginning of a level.
        self.level_label = None

        self.setup_level1()

        self.selected_tower = None
        
    def generate_new_robot(self):
        """Teleport a new robot into the map at the path starting point."""
        x, y = self.robot_waypoints[0]
        robot_class = random.choice([RedRobot, YellowRobot, GreenRobot, PurpleRobot, ArmorRobot])
        robot = robot_class(x, y)
        self.add_object(robot)
        self.robot_counter += 1
        robot.goal = self.robot_waypoints[0]

    def create_invalid_bitmap(self, filename):
        """Given the path to an image representing the invalid regions of a gamemap,
        this creates a bitmap suitable for doing quick queries to determine if a point
        on the map is valid or not.  The image should have valid regions represented by
        pixels with alpha = 0 and invalid areas drawn using pixels with alpha != 0."""

        self.invalid = resource.texture('graphics/level1_invalid.png', 0, 0)
        img = self.invalid.get_image_data()
        raw_data = img.get_data(img.format, img.pitch)

        # Extract only the alpha values from the image.
        alpha_data = ''
        bytes_per_pixel = len(img.format)
        for y in xrange(img.height):
            row_offset = y * img.pitch
            for x in xrange(img.width):
                byte = raw_data[row_offset+x*bytes_per_pixel+3]
                if ord(byte) < 128:
                    alpha_data += '\x00'
                else:
                    alpha_data += '\xff'

        self.invalid_bitmap = alpha_data

    def is_tower_invalid_at_point(self, x, y):
        """Returns true if a tower cannot be placed in the map at this position."""
        x = min(int(x * float(self.invalid.width) / float(self.width)), self.invalid.width-1)
        y = min(int(y * float(self.invalid.height) / float(self.height)), self.invalid.height-1)
        byte = self.invalid_bitmap[y*self.invalid.width + x]
        return byte != '\x00'

    def setup_level1(self):
        """This sets up the ohm-shaped level."""
        self.background = Sprite(resource.texture('graphics/level1.png', 0, 0))
        self.create_invalid_bitmap('graphics/level1_invalid.png')
        self.width = self.background.width
        self.height = self.background.height
                
        # Create level title
        self.level_label = pyglet.text.Label('Level 1', anchor_x='center', font_size=64, bold=True, x=320, y=320, color=(0,0,0,255))

        # Define the path that robots take as a list of waypoints.
        self.robot_waypoints = [
            Vec2d(688,132),
            Vec2d(416,175),
            Vec2d(510,345),
            Vec2d(482,485),
            Vec2d(378,566),
            Vec2d(256,560),
            Vec2d(141,446),
            Vec2d(141,328),
            Vec2d(220,174),
            Vec2d(-71,114)]
        
        # Zero out robot counter (incremented when new robots are added to level).
        self.robot_counter = 0
        self.robot_add_timer = 0
        self.robot_add_delay = 0.7  # time in seconds between new robots

        # Add in a tower.
        self.add_object(CannonTower(250,256))
                    
    def add_object(self, obj):
        """Add an object to the map."""
        obj.map = weakref.proxy(self)
        self.objects.append(obj)
        
    def remove_object(self, obj):
        """Schedule an object for removal from the map."""
        self.deleted_objects.add(obj)

    def remove_deleted_objects(self):
        """Remove objects scheduled for deletion."""
        while self.deleted_objects:
            obj = self.deleted_objects.pop()
            obj.map = None
            self.objects.remove(obj)

    def draw(self,debug=False):
        """Draws the map background and all the objects in the map."""

        glClear(GL_COLOR_BUFFER_BIT)
        glEnable(GL_BLEND)                    
        glEnable(GL_TEXTURE_2D)        
        self.background.draw()

        if self.selected_tower:
            self.selected_tower.draw_area_of_effect()

        # Sort the game objects before drawing.
        self.objects.sort(key=lambda obj: obj.position.y, reverse=True)

        # Draw the game objects.
        for obj in self.objects:
            obj.draw()

        if self.level_label:
            self.level_label.draw()

        # Draw the collision shapes of map objects.
        if debug:
            glDisable(GL_BLEND)                    
            glDisable(GL_TEXTURE_2D)
            for obj in self.objects:
                if hasattr(obj, 'rectangle'):
                    obj.rectangle().draw()
            glEnable(GL_TEXTURE_2D)
            glEnable(GL_BLEND)

    def update(self, dt):
        """Call update on each map object and perform other maintenance."""
        if dt > 0.03:
            dt = 0.03

        # Update all objects in the map.
        for obj in self.objects:
            obj.update(dt)

        # Remove any objects that were deleted during the update.
        self.remove_deleted_objects()

        # Add in new robots to level.
        self.robot_add_timer -= dt
        if self.robot_add_timer <= 0:
            self.generate_new_robot()
            self.robot_add_timer = self.robot_add_delay + random.uniform(-0.5,0.5)

        # Fade out level intro.
        if self.level_label:
            r,g,b,a = self.level_label.color
            a -= 2
            if a < 0:
                self.level_label = None
            else:
                self.level_label.color = (r,g,b,a)

    def point_over_scrap(self, x, y):
        for obj in self.objects:
            if isinstance(obj, RobotScrap) and obj.sprite.hit(x,y):
                return obj
        return False

    def point_over_tower(self, x, y):
        for obj in self.objects:
            if isinstance(obj, Tower) and obj.sprite.hit(x,y):
                return obj
        return False

    def rect_overlaps_tower(self, rect):
        # this is definitely not optimized for speed.
        for obj in self.objects:
            if isinstance(obj, Tower) and rect.overlaps(obj.rectangle()):
                return obj
        return False
